﻿/*
--------------------------------------
File: BinarySections.cs
Project: Themaopdracht 7
Author: Roel Blaauwgeers
Description:
Create a binary image in sections
Otsu threshold calculation is based on code found here: 
http://code.google.com/p/ex-ocr-video/
--------------------------------------
*/

using System;
using System.Drawing;

namespace THO7_Team8.Filters
{
    class BinarySectionFilter
    {
        private Boolean CHECK_OVERALL_THRESHOLD = true;
        private int testFieldDimensions;
        private int[] imageArray;
        private int imageWidth;
        private int imageHeight;
        private int imageThreshold;

        //factor a field threshold may differ from the overal threshold (minimal)
        private int minThresholdPercentace = 999;//80;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="ia"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public BinarySectionFilter(int[] ia, int width, int height)
        {
            this.imageArray = ia;
            this.imageWidth = width;
            this.imageHeight = height;
        }

        /// <summary>
        /// Main, walks trough image per section to calculate theshold per section and creates a binary image.
        /// </summary>
        /// <returns>image array (int[])</returns>
        public int[] doAlgorithm()
        {
            //set a usefull fieldsize
            testFieldDimensions = imageWidth / 12; //12 works good

            //calculate a threshold over the complete image
            if (CHECK_OVERALL_THRESHOLD)
                imageThreshold = getOtsuThreshold(makeHistogram(imageArray));

            //how many fields? Integers are rounded up: (m+n-1)/n
            //fields in a row
            int fieldsInARow = ((imageWidth + testFieldDimensions - 1) / testFieldDimensions);
            int fieldsInAColumn = ((imageHeight + testFieldDimensions - 1) / testFieldDimensions);
            int totalAmountOfFields = fieldsInARow * fieldsInAColumn;

            //holds the startingpoints of all fields
            int[] fieldCoordinates = new int[totalAmountOfFields];

            //get width of field at the right (can be smaller as a normal field)
            int smallestFieldWidth = imageWidth % testFieldDimensions;
            if (smallestFieldWidth == 0)
                smallestFieldWidth = testFieldDimensions;

            //get bottom field height (can be smaller as a normal field)
            int smallestFieldHeight = imageHeight % testFieldDimensions;
            if (smallestFieldHeight == 0)
                smallestFieldHeight = testFieldDimensions;

            int startPoint = 0;
            for (int i = 0; i < fieldsInAColumn; i++)
            {
                for (int j = 0; j < fieldsInARow; j++)
                {
                    //set the starting point of the field
                    startPoint = (i * imageWidth * testFieldDimensions + (testFieldDimensions * j));

                    //detect a field smaller than usual
                    if (fieldsInARow - 1 == j || fieldsInAColumn - 1 == i)
                    {

                        //field on the right (normal height)
                        if ((fieldsInARow - 1 == j) && (fieldsInAColumn - 1 != i))
                            processField(startPoint, smallestFieldWidth, testFieldDimensions);

                        //field on bottom (normal width)
                        else if ((fieldsInARow - 1 != j) && (fieldsInAColumn - 1 == i))
                            processField(startPoint, testFieldDimensions, smallestFieldHeight);

                        //field on bottom (small height and small width)
                        else if ((fieldsInARow - 1 == j) && (fieldsInAColumn - 1 == i))
                            processField(startPoint, smallestFieldWidth, smallestFieldHeight);
                    }
                    else
                        processField(startPoint, testFieldDimensions, testFieldDimensions);
                }
            }
            return imageArray;
        }

        /// <summary>
        /// Processes a field. Calculates threshold per field and converts the field
        /// </summary>
        /// <param name="startpoint"></param>
        /// <param name="fieldWidth"></param>
        /// <param name="fieldHeight"></param>
        /// <param name="yStepSize"></param>
        private void processField(int startpoint, int fieldWidth, int fieldHeight)
        {
            //calculate endpoint
            int endpoint = (startpoint + ((fieldHeight - 1) * imageWidth + fieldWidth - 1));

            //Console.WriteLine("END: " + endpoint);

            //create a histogram of the current field
            int[] bin = new int[256];
            int endOfRow;
            int z;
            for (int y = startpoint; y <= endpoint - fieldWidth + 1; y += imageWidth)
            {
                endOfRow = y + fieldWidth;
                z = y;
                for (; z < endOfRow; z++)
                {
                    int blue = imageArray[z] & 255;
                    int green = (imageArray[z] >> 8) & 255;
                    int red = (imageArray[z] >> 16) & 255;

                    bin[(int)(blue * 0.11 + green * 0.59 + red * 0.3)]++;
                }
            }

            //calculate the threshold of the field
            int threshold = getOtsuThreshold(bin);

            if (CHECK_OVERALL_THRESHOLD)
            {
                if (threshold < (imageThreshold * 100 / minThresholdPercentace))
                    threshold = imageThreshold;
            }

            //binarize!!!
            for (int y = startpoint; y <= endpoint - fieldWidth + 1; y += imageWidth)
            {
                endOfRow = y + fieldWidth;
                z = y;
                for (; z < endOfRow; z++)
                {

                    int blue = imageArray[z] & 255;
                    int green = (imageArray[z] >> 8) & 255;
                    int red = (imageArray[z] >> 16) & 255;

                    if ((int)(blue * 0.11 + green * 0.59 + red * 0.3) > threshold)
                        imageArray[z] = -1; //make white
                    else
                        imageArray[z] = -16777216;// make black
                }
            }
        }

        /// <summary>
        /// Creates histogram
        /// </summary>
        /// <param name="array"></param>
        /// <returns>histogram (int[])</returns>
        private int[] makeHistogram(int[] array)
        {

            int[] hist = new int[256];
            for (int i = 0; i < array.Length; i++)
            {
                int blue = imageArray[i] & 255;
                int green = (imageArray[i] >> 8) & 255;
                int red = (imageArray[i] >> 16) & 255;

                hist[(int)(blue * 0.11 + green * 0.59 + red * 0.3)]++;
            }
            return hist;
        }

        /*
         * THRESHOLD
         */

        /// <summary>
        /// Calculates the most usefull threshold
        /// </summary>
        /// <param name="hist"></param>
        /// <returns>Threshold (int)</returns>
        private int getOtsuThreshold(int[] hist)
        {
            byte t = 0;
            float[] vet = new float[256];
            vet.Initialize();

            float p1, p2, p12;
            int k;


            // loop through all possible t values and maximize between class variance
            for (k = 1; k != 255; k++)
            {
                p1 = Px(0, k, hist);
                p2 = Px(k + 1, 255, hist);
                p12 = p1 * p2;
                if (p12 == 0)
                    p12 = 1;
                float diff = (Mx(0, k, hist) * p2) - (Mx(k + 1, 255, hist) * p1);
                vet[k] = (float)diff * diff / p12;
                //vet[k] = (float)Math.Pow((Mx(0, k, hist) * p2) - (Mx(k + 1, 255, hist) * p1), 2) / p12;
            }


            t = (byte)findMax(vet, 256);

            return t;
        }

        /// <summary>
        /// function is used to compute the q values in the equation
        /// </summary>
        /// <param name="init"></param>
        /// <param name="end"></param>
        /// <param name="hist"></param>
        /// <returns></returns>
        private float Px(int init, int end, int[] hist)
        {
            int sum = 0;
            int i;
            for (i = init; i <= end; i++)
                sum += hist[i];

            return (float)sum;
        }

        /// <summary>
        /// function is used to compute the mean values in the equation (mu)
        /// </summary>
        /// <param name="init"></param>
        /// <param name="end"></param>
        /// <param name="hist"></param>
        /// <returns></returns>
        private float Mx(int init, int end, int[] hist)
        {
            int sum = 0;
            int i;
            for (i = init; i <= end; i++)
                sum += i * hist[i];

            return (float)sum;
        }

        /// <summary>
        /// Finds the maximum element in a vector
        /// </summary>
        /// <param name="vec">Vector to search</param>
        /// <param name="n">limit</param>
        /// <returns>The maximum element</returns>
        private int findMax(float[] vec, int n)
        {
            float maxVec = 0;
            int idx = 0;
            int i;

            for (i = 1; i < n - 1; i++)
            {
                if (vec[i] > maxVec)
                {
                    maxVec = vec[i];
                    idx = i;
                }
            }
            return idx;
        }
    }
}
